Make a map of species observations in under 5 minutes

The ability to make a map quickly is an essential skill in ecology and conservation. This post shows how to make a quick, simple map of Peron’s tree frog occurrences using galah-python, geopandas & matplotlib.

Eukaryota
Animalia
Chordata
Anura
Maps
Authors

Amanda Buyan

Dax Kellie

Published

November 23, 2023

Author

Amanda Buyan
Dax Kellie

Date

23 November 2023

Maps are one of the most common and useful data visualisation tools in an ecologist’s tool belt. Making a quick and simple map of species observations is especially useful when first investigating where a species has occurred. Viewing locations of points helps to understand the extent of your data (and spot possible errors or outliers).

In this post, we will use Python to make a map in under 5 minutes of Peron’s tree frog (Litoria peronii) observations in New South Wales, Australia recorded by FrogID since 2018 using the galah_python, geopandas and matplotlib packages.

Download data

Peron’s Tree frog is one of the most recorded frog species in the Atlas of Living Australia. Growing up to 7cm in length, it is well-known for its eyes which often look like they have a black cross on them!

First, let’s import galah-python.

import galah

To run a command asking galah-python to tell you the total number of records the Atlas of Living Australia has, use atlas_counts.

galah.atlas_counts()
   totalRecords
0     132243624

We can then narrow down the number of record counts by providing one or more of the following:

  • Species scientific name(s)
  • Filters

To find species’ scientific names, we suggest using the search_taxa() function of galah-python or by doing a Google search of your favourite species. To find other ways to narrow your query, you can use the galah.show_all() command, which will show you all possible fields to narrow search. However, if you want to search for a particular string of text in a field, use the galah.search_all() function.

import pandas as pd
galah.search_all(fields="year")
                    id                                        description   type                                               link
0                 year  The year in which an occurrence was observed. ...  field  https://github.com/AtlasOfLivingAustralia/ala-...
1         endDayOfYear          http://rs.tdwg.org/dwc/terms/endDayOfYear  field                                                NaN
2        datePrecision  The precision of the date information for the ...  field                                                NaN
3       occurrenceYear  Year ranges for a search. Calculated based on ...  field                                                NaN
4       startDayOfYear        http://rs.tdwg.org/dwc/terms/startDayOfYear  field                                                NaN
5  namePublishedInYear   http://rs.tdwg.org/dwc/terms/namePublishedInYear  field                                                NaN

If we were to choose year as our filter, the other things we need to do is to find what value we want to filter by. To find all possible values of year, use the show_values() function. To search for specific values, use the search_values() function.

Let’s map the locations of Peron’s tree frog since 2018 in New South Wales by FrogID. First, we will find how many records there are of Peron’s tree frog that match our query. It’s good practice check how many observations there are of a given species so you know how many to expect when you download them!

We’ll use atlas_counts() to download record counts, specifying the taxon we want using the taxa argument, and narrowing the year range, state/territory and data resource using the filters argument.

galah.atlas_counts(                      # *Download record counts*
    taxa="litoria peronii",              # *of Peron's tree frog*
    filters=["year>=2018",               # *since 2018*
             "cl22=New South Wales",     # *in New South Wales*
             "dataResourceName=FrogID"]  # *by FrogID*
)
   totalRecords
0         27647

Now we can use atlas_occurrences() to download occurrence records!

You will need to first provide a registered email with the ALA using galah_config() before retrieving records.

galah.galah_config(email = "your-email-here")
frogs = galah.atlas_occurrences(
    taxa="litoria peronii",
    filters=["year>=2018",
             "cl22=New South Wales",
             "dataResourceName=FrogID"]
)
frogs
       decimalLatitude  decimalLongitude             eventDate   scientificName                                     taxonConceptID                              recordID dataResourceName occurrenceStatus
0           -37.246800        149.375000  2020-12-27T00:00:00Z  Litoria peronii  https://biodiversity.org.au/afd/taxa/c584f24b-...  a5cd2fcd-5225-4d19-977c-b16ca5e8f1dd           FrogID          PRESENT
1           -37.089036        149.699526  2020-12-14T00:00:00Z  Litoria peronii  https://biodiversity.org.au/afd/taxa/c584f24b-...  eebde5ef-cac4-4897-af00-cb2e39a0684f           FrogID          PRESENT
2           -37.077693        149.874402  2018-01-06T00:00:00Z  Litoria peronii  https://biodiversity.org.au/afd/taxa/c584f24b-...  35340478-97c1-48a4-a463-991fe3a8daa0           FrogID          PRESENT
3           -37.077241        149.874787  2018-01-06T00:00:00Z  Litoria peronii  https://biodiversity.org.au/afd/taxa/c584f24b-...  a7abc9f3-362f-469e-9076-5b55a2447b69           FrogID          PRESENT
4           -37.070746        149.896011  2020-12-13T00:00:00Z  Litoria peronii  https://biodiversity.org.au/afd/taxa/c584f24b-...  1cc9dda8-f2d4-4f55-acf6-11c93b26da9e           FrogID          PRESENT
...                ...               ...                   ...              ...                                                ...                                   ...              ...              ...
27642       -28.207514        153.442592  2018-11-15T00:00:00Z  Litoria peronii  https://biodiversity.org.au/afd/taxa/c584f24b-...  b094fed1-5bff-4df8-b556-cabd693c533a           FrogID          PRESENT
27643       -28.207472        153.442497  2018-11-15T00:00:00Z  Litoria peronii  https://biodiversity.org.au/afd/taxa/c584f24b-...  5cc24fbd-8c6b-4a76-9b28-fec76ee08f37           FrogID          PRESENT
27644       -28.207442        153.442328  2020-02-07T00:00:00Z  Litoria peronii  https://biodiversity.org.au/afd/taxa/c584f24b-...  61aa50a1-4c79-4fc3-b3ab-93538faa37b1           FrogID          PRESENT
27645       -28.207108        153.443021  2021-02-19T00:00:00Z  Litoria peronii  https://biodiversity.org.au/afd/taxa/c584f24b-...  0324b8d1-77b0-4bf3-9ec2-6ad9efff18f2           FrogID          PRESENT
27646       -28.186157        153.445556  2018-11-16T00:00:00Z  Litoria peronii  https://biodiversity.org.au/afd/taxa/c584f24b-...  bcf83a54-a900-4265-960a-9436356a7107           FrogID          PRESENT

[27647 rows x 8 columns]

As we can see, we get 8 columns by default from the ALA; to make our map,however, we only need scientificName,decimalLatitude, and decimalLongitude columns.

Make a map

It’s time to make our map!

In order to draw our map of New South Wales, we’ll download a shapefile of the latest state and territory boundaries from the Australian Bureau of statistics (link here). Download the “States and Territories - 2021 - Shapefile” zip file, and save the zip file in the same folder you are coding in.

Let’s load our States and Territories shapefile with read_file() and save it as states. Then, we will choose the part of the shape that represents New South Wales. We will also specify that the edges of the shape are black, the inside of the shape is white, and the figure size is in inches, the default unit for matplotlib.

from matplotlib import pyplot as plt
import geopandas as gpd

# Load Australian state and territory boundaries
states = gpd.read_file("STE_2021_AUST_GDA94.shp")

# Filter to New South Wales and plot
states[states["STE_NAME21"] == "New South Wales"].plot(edgecolor = "#5A5A5A", linewidth = 0.5, facecolor = "white", figsize = (12,6))

We will also set the Coordinate References System (CRS), which determines how our points on the spherical globe are oriented when drawn as a flat surface. The projection of ALA data is EPSG:4326 (also known as “WGS84”). Setting the CRS allows us to make sure the points of our data align correctly with our shapefile.

states = states.to_crs(4326)

Now, we will add our species data to the map. First, we will plot the shapefile as shown in the codeblock above. Then, we will create a scatter plot using decimalLongitude as your x axis, and decimalLatitude as your y axis. c represents the colour the scatterplot is, and alpha is how transparent the dots are (1 is no transparency, 0 is fully transparent).

states[states["STE_NAME21"] == "New South Wales"].plot(edgecolor = "#5A5A5A", linewidth = 0.5, facecolor = "white", figsize = (12,6))
plt.scatter(frogs['decimalLongitude'],frogs['decimalLatitude'], c = "#6fab3f", alpha = 0.5)

For some final touches (to make the map prettier), we can add labels, titles and legends, as well as save the figure.

states[states["STE_NAME21"] == "New South Wales"].plot(edgecolor = "#5A5A5A", linewidth = 0.5, facecolor = "white", figsize = (12,6))
plt.scatter(frogs['decimalLongitude'],frogs['decimalLatitude'], c = "#6fab3f", alpha = 0.5, label = "Litoria peronii")
plt.legend()
plt.xlabel("Longitude",fontsize=16)
plt.ylabel("Latitude",fontsize=16)
plt.title("Peron's tree frog\nFrogID observations in New South Wales since 2018",fontsize=20)

To save your plot in your current folder, you can use:

plt.savefig("perons_tree_frog_nsw.png")
Code
import galah
from matplotlib import pyplot as plt
import geopandas as gpd

# Load Australian state and territory boundaries
states = gpd.read_file("STE_2021_AUST_GDA94.shp")

# Filter to New South Wales
states[states["STE_NAME21"] == "New South Wales"].plot(edgecolor = "#5A5A5A", linewidth = 0.5, facecolor = "white", figsize = (12,6))

# Change your Coordinate Reference System to the same as the ALA CRS
states = states.to_crs(4326)

# Plot your shape file
ax = states[states["STE_NAME21"] == "New South Wales"].plot(edgecolor = "#5A5A5A", linewidth = 0.5, facecolor = "white", figsize = (12,6))

# Plot your species points over the shape file
plt.scatter(frogs['decimalLongitude'],frogs['decimalLatitude'], c = "#6fab3f", alpha = 0.5)

# add a legend to show what species you are plotting
plt.legend()

# set X and Y label so users know they are looking at degrees latitude and longitude
ax.set_xlabel("Longitude",fontsize=16)
ax.set_ylabel("Latitude",fontsize=16)

# add a title to your plot
plt.title("Peron's tree frog\nFrogID observations in New South Wales since 2018",fontsize=20)

Final thoughts

We hope this post has helped make the basic steps of making a map simple and easy to understand. For more advanced mapping in Python, check out our ALA Labs article on how to map invasive species.

Expand for session info
-----
galah               0.7.0
geopandas           0.13.2
matplotlib          3.7.2
natsort             8.4.0
pandas              2.0.3
session_info        1.0.0
-----
Python 3.9.16 | packaged by conda-forge | (main, Feb  1 2023, 21:40:25) [Clang 14.0.6 ]
macOS-13.5.2-arm64-arm-64bit
-----
Session information updated at 2023-11-29 10:54